﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

using STSdb.Data;
using STSdb.Files;

namespace STSdb.MTL
{
    public interface IReadLocator
    {       
        XTable XTable { get; }
        /// <summary>
        /// Because transaction is finished,
        /// occuped read locators for this thread must be removed from reservation
        /// </summary>
        /// <param name="threadId"></param>
        void RemoveReadLocatorsForTheThread(int transactionThreadId);
    }
    public interface IWriteLocator
    {
        XTable XTable { get; }
    }
    /// <summary>
    /// Atom class responsible for one Logical XTable representation.
    /// Contains two locators, one for reading, second for writing. 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="T1"></typeparam>
    public class TableLocators<T, T1> : ITable<T, T1>, IReadLocator, IWriteLocator   //ITransactable
    {
        //Represents and STSdb XTable, with two Locators
        //One locator (READ_LOCATOR) is used for readings and has high concurrent access
        //Second locator (WRITE_LOCATOR) only for writing or READ_SYNCHRO threads, after commit new copy of READ_LOCATOR will be created.


        TransactionsCoordinator _transactionsCoordinator = null;
        string _tableName = String.Empty;
        /// <summary>
        /// Synchronizes tables switch from read to write.
        /// </summary>
        ReaderWriterLockSlim _sync_tablesSwitch = new ReaderWriterLockSlim();

        private XTable<T, T1> _write_table = null;
        //private XTable<T, T1> _read_table = null;

        ////Write table was used in session
        //bool SessionChangedTheData = false;

        //ulong _writeModificationCounter = 0;
        //object lock_writeModificationCounter = new object();


        public TableLocators(TransactionsCoordinator transactionsCoordinator,string tableName)
        {
            this._transactionsCoordinator = transactionsCoordinator;
            this._tableName = tableName;
            
            this.CreateNewWriteLocator();  
          
            
        }

        //int tableVersion = 0;
      

        /// <summary>
        /// Used after Table locators initiation. Or when table is restored after snapshot.
        /// </summary>
        /// <param name="tableName"></param>
        public void CreateNewWriteLocator()
        {
            //lock (kk)
            //{
                _sync_tablesSwitch.EnterWriteLock();
                try
                {


                    //lock (lock_writeModificationCounter)
                    //{
                    //    _writeModificationCounter++;
                    //}
                    //lock (this._transactionsCoordinator._SYNC_COMMIT)
                    //{
                        if (_write_table != null)
                            _write_table.Close();

                        this._write_table = this._transactionsCoordinator.SchemaCoordinator.OpenTable<T, T1>(this._tableName);
                    //}
                    this.SetupWriteLocator(this._write_table);

                    //tableVersion++;
                    //Console.WriteLine(String.Format("Table {0}; Version {1};", this._tableName, tableVersion));


                }
                catch (System.Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    _sync_tablesSwitch.ExitWriteLock();
                }
            //}
        }

     

        #region "Setup Locators"
    

        /// <summary>
        /// Setup Write locator
        /// </summary>
        /// <param name="writeLocator"></param>
        private void SetupWriteLocator(XTable<T, T1> writeLocator)
        {
            var tp = this._transactionsCoordinator.TablesCoordinator.GetTableProperties<T, T1>(this._tableName);
            if (tp != null)
            {
                if (tp.KeyMap != null)  //Checking if KeyMap Exists
                    writeLocator.KeyMap = tp.KeyMap;

                if (tp.RecordPersist != null)   //RecordPersist
                    writeLocator.RecordPersist = tp.RecordPersist;

                writeLocator.CacheCapacity = tp.CacheCapacity;
                writeLocator.BlockCapacity = tp.BlockCapacity;
            }
            else
            {
                writeLocator.CacheCapacity = TableProperties<T, T1>.DefaultCacheCapacity;
                writeLocator.BlockCapacity = TableProperties<T, T1>.DefaultBlockCapacity;
                //KeyMap and Persist remain emtpy
            }
        }

        /// <summary>
        /// Setups read locator
        /// </summary>
        /// <param name="readLocator"></param>
        private void SetUpReadLocator(XTable<T, T1> readLocator)
        {
            var tp = this._transactionsCoordinator.TablesCoordinator.GetTableProperties<T, T1>(this._tableName);
            if (tp != null)
            {
                if (tp.KeyMap != null)  //Checking if KeyMap Exists
                    readLocator.KeyMap = tp.KeyMap;

                if (tp.RecordPersist != null)   //RecordPersist
                    readLocator.RecordPersist = tp.RecordPersist;

                readLocator.CacheCapacity = tp.CacheCapacity;
                readLocator.BlockCapacity = tp.BlockCapacity;
            }
            else
            {
                readLocator.CacheCapacity = TableProperties<T, T1>.DefaultCacheCapacity;
                readLocator.BlockCapacity = TableProperties<T, T1>.DefaultReadBlockCapacity;
                //KeyMap and Persist remain emtpy
            }
        }

        #endregion


        public XTable<T, T1> GET_WRITE_TABLE()
        {
            //lock (kk)
            //{
                _sync_tablesSwitch.EnterReadLock();
                try
                {
                    //Console.WriteLine(String.Format("Acquired: Table {0}; Version {1}; Thread: {2}", this._tableName, tableVersion, System.Threading.Thread.CurrentThread.ManagedThreadId));

                    //SessionChangedTheData = true;
                    return _write_table;
                }
                catch (System.Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    _sync_tablesSwitch.ExitReadLock();
                }
            //}
        }

        /// <summary>
        /// Returns Write Table as for transaction (internally used by TransactionUnit only to execute native STSdb transaction.commit)
        /// </summary>
        public ITransaction STSdbITransaction
        {
            get
            {
                //lock (kk)
                //{
                    _sync_tablesSwitch.EnterReadLock();
                    try
                    {
                        return this._write_table;
                    }
                    catch (System.Exception ex)
                    {
                        throw ex;
                    }
                    finally
                    {
                        _sync_tablesSwitch.ExitReadLock();
                    }
                //}
            }
        }

        public string TableName
        {
            get { return this._tableName; }
        }


        #region "Handling creation of ReadLocators for different Threads"

        Dictionary<int, XTable<T, T1>> _readLocators = new Dictionary<int, XTable<T, T1>>();
        ReaderWriterLockSlim _sync_readLocators = new ReaderWriterLockSlim();
        //ulong _readModificationCounter = 0;

        /// <summary>
        /// Returns read locator. Due to the specific that different threads can't share one read locator, 
        /// for every thread its own instance will be created and supported.
        /// Implemented logic, that if transaction "touched" read locator, it will be used till the end of transaction
        /// </summary>
        /// <param name="transactionThreadId"></param>
        /// <returns></returns>
        public XTable<T, T1> GetReadTableLocator(int transactionThreadId)
        {



            //bool createNewReadLocator = false;

            _sync_readLocators.EnterUpgradeableReadLock();
            try
            {
                //After transaction is finished transaction thread id will be removed from the list.
                //But if transaction once requested Read-Locator, it will be used till the transaction ends.
                if (_readLocators.ContainsKey(transactionThreadId))
                {
                    //Console.WriteLine(String.Format("READ EXIST: Table {0}; Version {1}; Thread: {2}", this._tableName, tableVersion, System.Threading.Thread.CurrentThread.ManagedThreadId)); 
                    return _readLocators[transactionThreadId];
                }
                else
                {
                    _sync_readLocators.EnterWriteLock();
                    try
                    {
                        var readLocator = this._transactionsCoordinator.SchemaCoordinator.OpenTable<T, T1>(this._tableName);
                        this.SetUpReadLocator(readLocator);
                        _readLocators.Add(transactionThreadId, readLocator);

                        //Console.WriteLine(String.Format("READ NEW: Table {0}; Version {1}; Thread: {2}", this._tableName, tableVersion, System.Threading.Thread.CurrentThread.ManagedThreadId)); 

                        return readLocator;
                    }
                    catch (System.Exception ex)
                    {
                        throw ex;
                    }
                    finally
                    {
                        _sync_readLocators.ExitWriteLock();
                    }
                }



                //lock (lock_writeModificationCounter)
                //{
                //    if (_readModificationCounter != _writeModificationCounter)
                //    {
                //        _readModificationCounter = _writeModificationCounter;
                //        createNewReadLocator = true;
                //    }
                //}

                //if (!createNewReadLocator && _readLocators.ContainsKey(transactionThreadId))
                //{
                //    return _readLocators[transactionThreadId];
                //}
                //else
                //{
                //    _sync_readLocators.EnterWriteLock();
                //    try
                //    {
                //        if (_readLocators.ContainsKey(transactionThreadId))
                //        {
                //            _readLocators[transactionThreadId].Close();
                //            _readLocators.Remove(transactionThreadId);
                //        }

                //        var readLocator = this._transactionsCoordinator.SchemaCoordinator.OpenTable<T, T1>(this._tableName);
                //        this.SetUpReadLocator(readLocator);
                //        _readLocators.Add(transactionThreadId, readLocator);

                //        return readLocator;
                //    }
                //    catch (System.Exception ex)
                //    {
                //        throw ex;
                //    }
                //    finally
                //    {
                //        _sync_readLocators.ExitWriteLock();
                //    }
                //}
            }
            catch (System.Exception ex)
            {
                throw ex;
            }
            finally
            {
                _sync_readLocators.ExitUpgradeableReadLock();
            }
        }

        /// <summary>
        /// Gracefully closes all read locators for the thread, because transaction is finished
        /// </summary>
        /// <param name="transactionThreadId"></param>
        private void RemoveReadLocatorsDueToTransactionIsFinished(int transactionThreadId)
        {
            _sync_readLocators.EnterWriteLock();
            try
            {
                if (_readLocators.ContainsKey(transactionThreadId))
                {
                    _readLocators[transactionThreadId].Close();
                    _readLocators.Remove(transactionThreadId);
                }             
            }
            catch (System.Exception ex)
            {
                throw ex;
            }
            finally
            {
                _sync_readLocators.ExitWriteLock();
            }
        }

        #endregion

               


        /// <summary>
        /// Commits changes for this table and switches content
        /// </summary>
        public void Commit()
        {
            //This counter will be like a notifier for read locators to be created from new, because content of the table has changed
            //lock (lock_writeModificationCounter)
            //{
            //    _writeModificationCounter++;
            //}

           

        }


        #region "Automatic compacter statistic counter"

        int intensionsToModifyRecord = 0;

        /// <summary>
        /// Calculate intension to modify the table.
        /// If this intention is high enough for Table Compacting checking. Compaction will be called.
        /// Compaction will be done only in case if coresponds to compaction startegy rules.
        /// </summary>
        /// <param name="checkCompactInAnyCase">compacter will check table in any case. It can happen in case of Delete many keys, Clear all table etc.</param>
        public void IntentToModifyRecord(bool checkCompactInAnyCase)
        {
            if (checkCompactInAnyCase)
                intensionsToModifyRecord = StsTablesCompactor._MIN_QUANTITY_FOR_COMPACTION_START;
            else
                intensionsToModifyRecord++;

            if (intensionsToModifyRecord >= StsTablesCompactor._MIN_QUANTITY_FOR_COMPACTION_START)
            {
                //we must run compacter
                intensionsToModifyRecord = 0;
                this._transactionsCoordinator.SchemaCoordinator.StsTablesSchema.CompactTable(this._tableName);
            }
        }
        #endregion






        /// <summary>
        /// Distributed Transactions. Creates snapshot from Read locator using transaction Id
        /// </summary>
        public void SnapshotReadLocator(long transactionId)
        {
            //_sync_tablesSwitch.EnterReadLock();
            //try
            //{
            //    STSdb.Data.Locator tableSnapshotLocator = new Locator("@sys_trs_" + transactionId.ToString() + "_" + this._tableName);
            //    this._transactionsCoordinator.SchemaCoordinator.RegisterSnapshotTable<T, T1>(tableSnapshotLocator, this._read_table.Snapshot());
            //}
            //catch (System.Exception ex)
            //{
            //    throw ex;
            //}
            //finally
            //{
            //    _sync_tablesSwitch.ExitReadLock();
            //}  
        }
                

        /// <summary>
        /// Distributed Transactions. Restores read and write locator for the table from the snapshot by transactionId
        /// </summary>
        /// <param name="transactionId"></param>
        public void RestoreLocatorsFromSnapshot(long transactionId)
        {
            //_sync_tablesSwitch.EnterWriteLock();
            //try
            //{
            //    SessionChangedTheData = false;

            //    ////////////// @sys_trs_ can't be open via OpenTable, it's hidden from user's eyes, to make it visible look in Schema Coordinator Open File code and GetRealTableNameFromExhaled

            //    if (!this._transactionsCoordinator.SchemaCoordinator.IF_TableExists("@sys_trs_" + transactionId.ToString() + "_" + this._tableName))
            //        return;

            //    var transactionSnapshot = this._transactionsCoordinator.SchemaCoordinator.OpenTable<T, T1>("@sys_trs_" + transactionId.ToString() + "_" + this._tableName);
            //    this._transactionsCoordinator.SchemaCoordinator.RegisterSnapshotTable<T, T1>(new Locator(this._tableName), transactionSnapshot);
                
            //    this._read_table = this._transactionsCoordinator.SchemaCoordinator.OpenTable<T, T1>(this._tableName);
            //    this._write_table = this._transactionsCoordinator.SchemaCoordinator.OpenTable<T, T1>(this._tableName);

            //    //Setting up received locators 
            //    this.SetUpLocators(false);

            //    //Deleting transaction table, later if engine restarts after the failure and this table is not found, we can think that actual table has correct data
            //    //and can go on to work with it
            //    this._transactionsCoordinator.SchemaCoordinator.DeleteTable("@sys_trs_" + transactionId.ToString() + "_" + this._tableName);
            //}
            //catch (System.Exception ex)
            //{
            //    throw ex;
            //}
            //finally
            //{
            //    _sync_tablesSwitch.ExitWriteLock();
            //}
        
        }


        /// <summary>
        /// Not commonly used.
        /// </summary>
        public void RollBack()
        {
            ////here we have to create new locator for write !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  better only in case if table was really mutated to recreate locator, but we will see this later.

            //_sync_tablesSwitch.EnterWriteLock();
            //try
            //{

            //    //next line SessionChangedTheData = false is safe, table is registered for mutation till the end of transaction and not accessible for other threads.
            //    SessionChangedTheData = false;
            //    this.CreateNewWriteLocator();
            //}
            //catch (System.Exception ex)
            //{
            //    throw ex;
            //}
            //finally
            //{
            //    _sync_tablesSwitch.ExitWriteLock();
            //}

        }


        /// <summary>
        /// !!!!!!!!!!!!!!!!!!!!!!!!!    NOTE, THIS WILL BE CALLED ONLY IN CASE WHEN TRANSACTION WAS WRITING OR READ_SYNCHRO IN THIS TABLE
        /// !!!!!!!!!!!!!!!!!!!!!!!!!    Also if transaction is finished (using this table READ locators) can be known by calling IReadLocator.RemoveReadLocatorsForTheThread
        /// !!! May be one day put them together, but for now - no necessity
        /// </summary>
        /// <param name="transactionThreadId"></param>
        public void TransactionIsFinished(int transactionThreadId)
        {
            //We need to change Write Locator after Transaction who changed it is finished, for the case if transaction wrote in the table some data, but didn't commit it

            //lock (this._transactionsCoordinator._SYNC_COMMIT)
            //{
            CreateNewWriteLocator();
            //}

            //_sync_tablesSwitch.EnterWriteLock();
            //try
            //{
            //    this._write_table.Rollback();
            //}
            //catch (System.Exception ex)
            //{
            //    throw ex;
            //}
            //finally
            //{
            //    _sync_tablesSwitch.ExitWriteLock();
            //}
        }



        XTable IReadLocator.XTable
        {
            get {

                var xTable = new XTable(this.GetReadTableLocator(System.Threading.Thread.CurrentThread.ManagedThreadId));               
                return xTable;              
            }
        }

        XTable IWriteLocator.XTable
        {
            get
            {
                //lock (kk)
                //{
                    _sync_tablesSwitch.EnterWriteLock();
                    try
                    {
                        var xTable = new XTable(this._write_table);
                        return xTable;
                    }
                    catch (System.Exception ex)
                    {
                        throw ex;
                    }
                    finally
                    {
                        _sync_tablesSwitch.ExitWriteLock();
                    }
                //}
              
            }
        }


        void IReadLocator.RemoveReadLocatorsForTheThread(int transactionThreadId)
        {
            //WILL NOT WORK GOOD FOR READING LOCATORS, because is called only if transaction was modifying the table
            this.RemoveReadLocatorsDueToTransactionIsFinished(transactionThreadId);
        }
    }
}
